// Copyright (C) 2010 Félix Cloutier.

// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, version 2.0.

// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License 2.0 for more details.

// A copy of the GPL 2.0 should have been included with the program.
// If not, see http://www.gnu.org/licenses/

#include "Jit.h"
#include <llvm/ExecutionEngine/ExecutionEngine.h>
#include <llvm/Support/TypeBuilder.h>

#undef Yield // just in case WinBase.h got to here

namespace LLVMJit
{

namespace
{
	// Wrappers, because we can't otherwise guarantee LLVM will use the correct
	//	calling convention for C++ method calls (gcc/clang use the regular CC,
	//	MS uses thiscall)
	void SetExpectedNPC(JitExecutor* executor, u32 npc)
	{
		executor->SetExpectedNPC(npc);
	}
	
	void YieldMaybe(Coroutine* coroutine, u32 cycles)
	{
		if (cycles >= JitExecutor::eventLoopCycleTreshold)
			coroutine->Yield();
	}
	
	void Yield(Coroutine* coroutine)
	{
		coroutine->Yield();
	}
	
	u32 RunStub(JitExecutor* executor, Coroutine* coroutine, u32 address)
	{
		return executor->RunStub(coroutine, address);
	}
	
	void Break(Coroutine* coroutine, JitExecutor* executor, u32 npc)
	{
		executor->SetExpectedNPC(npc);
		coroutine->Yield();
	}
	
	void InvalidateInstructionCache(JitExecutor* executor, u32 address)
	{
		executor->ClearCache(address);
		PowerPC::ppcState.iCache.Invalidate(address);
	}
}

Jit::Jit()
{
	recompiler = NULL;
	executor = NULL;
}

Jit::~Jit()
{
	Shutdown();
}

void Jit::Init()
{
	llvm::Module* module = new llvm::Module("Dolphin PowerPC Code", context);
	llvm::ExecutionEngine* engine = llvm::EngineBuilder(module).create();
	
	recompiler = new JitRecompiler(module, engine->getTargetData());
	executor = new JitExecutor(engine, recompiler);
	
	JitBindings* bindings = executor->GetBindings();
	
	// disguise class pointers as i8 pointers for the sake of simplicity
	const llvm::Type* u8Type =
		llvm::TypeBuilder<llvm::types::i<8>, false>::get(context);
	const llvm::Type* u32Type =
		llvm::TypeBuilder<llvm::types::i<32>, false>::get(context);
	const llvm::Type* u8PointerType = llvm::PointerType::get(u8Type, 0);
	
	const llvm::FunctionType* void_u8Pointer = JitRecompiler::CreateFunctionType(
		llvm::Type::getVoidTy(context), u8PointerType, NULL);
	
	const llvm::FunctionType* void_u8Pointer_u32Type = JitRecompiler::CreateFunctionType(
		llvm::Type::getVoidTy(context), u8PointerType, u32Type, NULL);
	
	const llvm::FunctionType* u32_u8Pointer_u32Type = JitRecompiler::CreateFunctionType(
		u32Type, u8PointerType, u32Type, NULL);
	
	const llvm::FunctionType* u32_u8Pointer_u8Pointer_u32Type =
		JitRecompiler::CreateFunctionType(
			u32Type, u8PointerType, u8PointerType, u32Type, NULL);
	
	const llvm::FunctionType* void_u8Pointer_u8Pointer_u32Type =
		JitRecompiler::CreateFunctionType(
			llvm::Type::getVoidTy(context), u8PointerType, u8PointerType, u32Type, NULL);
	
	llvm::Function* setExpectedNPC = llvm::Function::Create(
		void_u8Pointer_u32Type,
		llvm::Function::ExternalLinkage,
		"SetExpectedNPC",
		module);
	
	llvm::Function* yieldMaybe = llvm::Function::Create(
		void_u8Pointer_u32Type,
		llvm::Function::ExternalLinkage,
		"YieldMaybe",
		module);
	
	llvm::Function* yield = llvm::Function::Create(
		void_u8Pointer,
		llvm::Function::ExternalLinkage,
		"Yield",
		module);
	
	llvm::Function* runStub = llvm::Function::Create(
		u32_u8Pointer_u32Type,
		llvm::Function::ExternalLinkage,
		"RunStub",
		module);
	
	llvm::Function* break_ = llvm::Function::Create(
		void_u8Pointer_u8Pointer_u32Type,
		llvm::Function::ExternalLinkage,
		"Break",
		module);
	
	llvm::Function* invalidateCache = llvm::Function::Create(
		void_u8Pointer_u32Type,
		llvm::Function::ExternalLinkage,
		"InvalidateInstructionCache",
		module);
	
	bindings->Bind(setExpectedNPC, (void*)SetExpectedNPC, JitBindings::SetExpectedNPC);
	bindings->Bind(yieldMaybe, (void*)YieldMaybe, JitBindings::YieldMaybe);
	bindings->Bind(yield, (void*)Yield, JitBindings::Yield);
	bindings->Bind(runStub, (void*)RunStub, JitBindings::RunStub);
	bindings->Bind(break_, (void*)Break, JitBindings::Break);
	bindings->Bind(invalidateCache, (void*)InvalidateInstructionCache, JitBindings::InvalidateInstructionCache);
	
	recompiler->SetBindings(executor->GetBindings());
}

void Jit::Shutdown()
{
	delete recompiler;
	delete executor;
	delete currentModule;
	recompiler = NULL;
	executor = NULL;
	currentModule = NULL;
}

void Jit::ClearCache()
{
	executor->ClearCache();
	recompiler->ClearCache();
}

void Jit::Run()
{
	while (PowerPC::GetState() == PowerPC::CPU_RUNNING)
		executor->Resume();
}

const char* Jit::GetName()
{
	return "LLVM JIT";
}

}